home *** CD-ROM | disk | FTP | other *** search
/ Visual Basic Graphics Programming (2nd Edition) / Visual Basic Graphics Programming 2nd Edition.iso / Src / Ch16 / Face3.cls < prev    next >
Text File  |  1999-06-28  |  17KB  |  604 lines

  1. VERSION 1.0 CLASS
  2. BEGIN
  3.   MultiUse = -1  'True
  4.   Persistable = 0  'NotPersistable
  5.   DataBindingBehavior = 0  'vbNone
  6.   DataSourceBehavior  = 0  'vbNone
  7.   MTSTransactionMode  = 0  'NotAnMTSObject
  8. END
  9. Attribute VB_Name = "Face3d"
  10. Attribute VB_GlobalNameSpace = False
  11. Attribute VB_Creatable = False
  12. Attribute VB_PredeclaredId = False
  13. Attribute VB_Exposed = False
  14. Option Explicit
  15.  
  16. ' Point3D is defined in module M3OPS.BAS as:
  17. '    Type Point3D
  18. '        coord(1 To 4) As Single
  19. '        trans(1 To 4) As Single
  20. '    End Type
  21.  
  22. Public NumPts As Long       ' Number of points.
  23. Private Points() As Point3D ' Data points.
  24.  
  25. Public IsCulled As Boolean
  26.  
  27. Private Type POINTAPI
  28.     X As Long
  29.     Y As Long
  30. End Type
  31. Private Declare Function Polygon Lib "gdi32" (ByVal hdc As Long, lpPoint As POINTAPI, ByVal nCount As Long) As Long
  32.  
  33. ' Diffuse reflection coefficients.
  34. Public DiffuseKr As Single
  35. Public DiffuseKg As Single
  36. Public DiffuseKb As Single
  37.  
  38. ' Ambient light coefficients.
  39. Public AmbientKr As Single
  40. Public AmbientKg As Single
  41. Public AmbientKb As Single
  42.  
  43. ' Return True if this polygon partially obscures
  44. ' (has greater Z value than) polygon target.
  45. '
  46. ' We assume one polygon may obscure the other, but
  47. ' they cannot obscure each other.
  48. '
  49. ' This check is executed by seeing where the
  50. ' projections of the edges of the polygons cross.
  51. ' Where they cross, see if one Z value is greater
  52. ' than the other.
  53. '
  54. ' If no edges cross, see if one polygon contains
  55. ' the other. If so, there is an overlap.
  56. Public Function Obscures(ByVal target As Face3d) As Boolean
  57. Dim num As Integer
  58. Dim i As Integer
  59. Dim j As Integer
  60. Dim xi1 As Single
  61. Dim yi1 As Single
  62. Dim zi1 As Single
  63. Dim xi2 As Single
  64. Dim yi2 As Single
  65. Dim zi2 As Single
  66. Dim xj1 As Single
  67. Dim yj1 As Single
  68. Dim zj1 As Single
  69. Dim xj2 As Single
  70. Dim yj2 As Single
  71. Dim zj2 As Single
  72. Dim X As Single
  73. Dim Y As Single
  74. Dim z1 As Single
  75. Dim z2 As Single
  76.  
  77.     num = target.NumPts
  78.  
  79.     ' Check each edge in this polygon.
  80.     GetTransformedPoint NumPts, xi1, yi1, zi1
  81.     For i = 1 To NumPts
  82.         GetTransformedPoint i, xi2, yi2, zi2
  83.  
  84.         ' Compare with each edge in the other.
  85.         target.GetTransformedPoint num, xj1, yj1, zj1
  86.         For j = 1 To num
  87.             target.GetTransformedPoint j, xj2, yj2, zj2
  88.             ' See if the segments cross.
  89.             If FindCrossing( _
  90.                 xi1, yi1, zi1, _
  91.                 xi2, yi2, zi2, _
  92.                 xj1, yj1, zj1, _
  93.                 xj2, yj2, zj2, _
  94.                 X, Y, z1, z2) _
  95.             Then
  96.                 If z1 - z2 > 0.01 Then
  97.                     ' z1 > z2. We obscure it.
  98.                     Obscures = True
  99.                     Exit Function
  100.                 End If
  101.                 If z2 - z1 > 0.01 Then
  102.                     ' z2 > z1. It obscures us.
  103.                     Obscures = False
  104.                     Exit Function
  105.                 End If
  106.             End If
  107.  
  108.             xj1 = xj2
  109.             yj1 = yj2
  110.             zj1 = zj2
  111.         Next j
  112.  
  113.         xi1 = xi2
  114.         yi1 = yi2
  115.         zi1 = zi2
  116.     Next i
  117.     
  118.     ' No edges cross. See if one polygon contains
  119.     ' the other.
  120.  
  121.     ' If any points of one polygon are inside the
  122.     ' other, then they must all be. Since the
  123.     ' IsAbove tests were inconclusive, some points
  124.     ' in one polygon are on the "bad" side of the
  125.     ' other. In that case there is an overlap.
  126.  
  127.     ' See if this polygon is inside the other.
  128.     GetTransformedPoint 1, xi1, yi1, zi1
  129.     If target.PointInside(xi1, yi1) Then
  130.         Obscures = True
  131.         Exit Function
  132.     End If
  133.  
  134.     ' See if the other polygon is inside this one.
  135.     target.GetTransformedPoint 1, xi1, yi1, zi1
  136.     If PointInside(xi1, yi1) Then
  137.         Obscures = True
  138.         Exit Function
  139.     End If
  140.  
  141.     Obscures = False
  142. End Function
  143. ' See where the projections of two segments cross.
  144. ' Return true if the segments cross, false
  145. ' otherwise.
  146. Private Function FindCrossing( _
  147.     ByVal ax1 As Single, ByVal ay1 As Single, ByVal az1 As Single, _
  148.     ByVal ax2 As Single, ByVal ay2 As Single, ByVal az2 As Single, _
  149.     ByVal bx1 As Single, ByVal by1 As Single, ByVal bz1 As Single, _
  150.     ByVal bx2 As Single, ByVal by2 As Single, ByVal bz2 As Single, _
  151.     ByRef X As Single, ByRef Y As Single, ByRef z1 As Single, ByRef z2 As Single) _
  152.         As Boolean
  153. Dim dxa As Single
  154. Dim dya As Single
  155. Dim dza As Single
  156. Dim dxb As Single
  157. Dim dyb As Single
  158. Dim dzb As Single
  159. Dim t1 As Single
  160. Dim t2 As Single
  161. Dim denom As Single
  162.  
  163.     dxa = ax2 - ax1
  164.     dya = ay2 - ay1
  165.     dxb = bx2 - bx1
  166.     dyb = by2 - by1
  167.     
  168.     FindCrossing = False
  169.     
  170.     denom = dxb * dya - dyb * dxa
  171.     ' If the segments are parallel, stop.
  172.     If denom < 0.01 And denom > -0.01 Then Exit Function
  173.  
  174.     t2 = (ax1 * dya - ay1 * dxa - bx1 * dya + by1 * dxa) / denom
  175.     If t2 < 0 Or t2 > 1 Then Exit Function
  176.     
  177.     t1 = (ax1 * dyb - ay1 * dxb - bx1 * dyb + by1 * dxb) / denom
  178.     If t1 < 0 Or t1 > 1 Then Exit Function
  179.  
  180.     ' Compute the points of overlap.
  181.     X = ax1 + t1 * dxa
  182.     Y = ay1 + t1 * dya
  183.     dza = az2 - az1
  184.     dzb = bz2 - bz1
  185.     z1 = az1 + t1 * dza
  186.     z2 = bz1 + t2 * dzb
  187.     FindCrossing = True
  188. End Function
  189.  
  190. ' Return True if the point projection lies within
  191. ' this polygon's projection.
  192. Public Function PointInside(ByVal X As Single, ByVal Y As Single) As Boolean
  193. Dim i As Integer
  194. Dim theta1 As Double
  195. Dim theta2 As Double
  196. Dim dtheta As Double
  197. Dim dx As Double
  198. Dim dy As Double
  199. Dim angles As Double
  200.  
  201.     dx = Points(NumPts).trans(1) - X
  202.     dy = Points(NumPts).trans(2) - Y
  203.     theta1 = ATan2(CSng(dy), CSng(dx))
  204.     If theta1 < 0 Then theta1 = theta1 + 2 * PI
  205.     For i = 1 To NumPts
  206.         dx = Points(i).trans(1) - X
  207.         dy = Points(i).trans(2) - Y
  208.         theta2 = ATan2(CSng(dy), CSng(dx))
  209.         If theta2 < 0 Then theta2 = theta2 + 2 * PI
  210.         dtheta = theta2 - theta1
  211.         If dtheta > PI Then dtheta = dtheta - 2 * PI
  212.         If dtheta < -PI Then dtheta = dtheta + 2 * PI
  213.         angles = angles + dtheta
  214.         theta1 = theta2
  215.     Next i
  216.  
  217.     PointInside = (Abs(angles) > 0.001)
  218. End Function
  219.  
  220. ' Return True if this polygon is completly above
  221. ' the plane containing target.
  222. Public Function IsAbove(ByVal target As Face3d) As Boolean
  223. Dim Nx As Single
  224. Dim Ny As Single
  225. Dim Nz As Single
  226. Dim px As Single
  227. Dim py As Single
  228. Dim pz As Single
  229. Dim dx As Single
  230. Dim dy As Single
  231. Dim dz As Single
  232. Dim Cx As Single
  233. Dim Cy As Single
  234. Dim Cz As Single
  235. Dim i As Integer
  236.  
  237.     ' Compute an upward pointing normal to the plane.
  238.     target.TransformedNormalVector Nx, Ny, Nz
  239.     If Nz < 0 Then
  240.         Nx = -Nx
  241.         Ny = -Ny
  242.         Nz = -Nz
  243.     End If
  244.  
  245.     ' Get a point on the plane.
  246.     target.GetTransformedPoint 1, px, py, pz
  247.  
  248.     ' See if the points in this polygon all lie
  249.     ' above the plane containing target.
  250.     For i = 1 To NumPts
  251.         ' Get the vector from plane to point.
  252.         dx = Points(i).trans(1) - px
  253.         dy = Points(i).trans(2) - py
  254.         dz = Points(i).trans(3) - pz
  255.  
  256.         ' If the dot product < 0, the point is
  257.         ' below the plane.
  258.         If dx * Nx + dy * Ny + dz * Nz < -0.01 Then
  259.             IsAbove = False
  260.             Exit Function
  261.         End If
  262.     Next i
  263.     IsAbove = True
  264. End Function
  265. ' Return true if this polygon is completly below
  266. ' the plane containing target.
  267. Public Function IsBelow(ByVal target As Face3d) As Boolean
  268. Dim Nx As Single
  269. Dim Ny As Single
  270. Dim Nz As Single
  271. Dim px As Single
  272. Dim py As Single
  273. Dim pz As Single
  274. Dim dx As Single
  275. Dim dy As Single
  276. Dim dz As Single
  277. Dim Cx As Single
  278. Dim Cy As Single
  279. Dim Cz As Single
  280. Dim i As Integer
  281.  
  282.     ' Compute a downward pointing normal to the plane.
  283.     target.TransformedNormalVector Nx, Ny, Nz
  284.     If Nz > 0 Then
  285.         Nx = -Nx
  286.         Ny = -Ny
  287.         Nz = -Nz
  288.     End If
  289.  
  290.     ' Get a point on the plane.
  291.     target.GetTransformedPoint 1, px, py, pz
  292.  
  293.     ' See if the points in this polygon all lie
  294.     ' below the plane containing target.
  295.     For i = 1 To NumPts
  296.         ' Get the vector from plane to point.
  297.         dx = Points(i).trans(1) - px
  298.         dy = Points(i).trans(2) - py
  299.         dz = Points(i).trans(3) - pz
  300.  
  301.         ' If the dot product < 0, the point is
  302.         ' below the plane.
  303.         If dx * Nx + dy * Ny + dz * Nz < -0.01 Then
  304.             IsBelow = False
  305.             Exit Function
  306.         End If
  307.     Next i
  308.     IsBelow = True
  309. End Function
  310. ' Return the transformed coordinates of a point
  311. ' on the polygon.
  312. Public Sub GetTransformedPoint(ByVal Index As Long, ByRef X As Single, ByRef Y As Single, ByRef Z As Single)
  313.     X = Points(Index).trans(1)
  314.     Y = Points(Index).trans(2)
  315.     Z = Points(Index).trans(3)
  316. End Sub
  317. ' Return the bounds of this polygon.
  318. Public Sub GetExtent(ByRef xmin As Single, ByRef xmax As Single, ByRef ymin As Single, ByRef ymax As Single, ByRef zmin As Single, ByRef zmax As Single)
  319. Dim i As Integer
  320.  
  321.     If NumPts < 1 Then Exit Sub
  322.  
  323.     With Points(1)
  324.         xmin = .trans(1)
  325.         xmax = xmin
  326.         ymin = .trans(2)
  327.         ymax = ymin
  328.         zmin = .trans(3)
  329.         zmax = zmin
  330.     End With
  331.  
  332.     For i = 2 To NumPts
  333.         With Points(i)
  334.             If xmin > .trans(1) Then xmin = .trans(1)
  335.             If xmax < .trans(1) Then xmax = .trans(1)
  336.             If ymin > .trans(2) Then ymin = .trans(2)
  337.             If ymax < .trans(2) Then ymax = .trans(2)
  338.             If zmin > .trans(3) Then zmin = .trans(3)
  339.             If zmax < .trans(3) Then zmax = .trans(3)
  340.         End With
  341.     Next i
  342. End Sub
  343.  
  344.  
  345. ' Compute a normal vector for this polygon.
  346. Public Sub NormalVector(ByRef Nx As Single, ByRef Ny As Single, ByRef Nz As Single)
  347. Dim Ax As Single
  348. Dim Ay As Single
  349. Dim Az As Single
  350. Dim Bx As Single
  351. Dim By As Single
  352. Dim Bz As Single
  353.  
  354.     Ax = Points(2).coord(1) - Points(1).coord(1)
  355.     Ay = Points(2).coord(2) - Points(1).coord(2)
  356.     Az = Points(2).coord(3) - Points(1).coord(3)
  357.     Bx = Points(3).coord(1) - Points(2).coord(1)
  358.     By = Points(3).coord(2) - Points(2).coord(2)
  359.     Bz = Points(3).coord(3) - Points(2).coord(3)
  360.     m3Cross Nx, Ny, Nz, Ax, Ay, Az, Bx, By, Bz
  361. End Sub
  362. ' Compute a unit normal vector for this polygon.
  363. Public Sub UnitNormalVector(ByRef Nx As Single, ByRef Ny As Single, ByRef Nz As Single)
  364. Dim length As Single
  365.  
  366.     NormalVector Nx, Ny, Nz
  367.     length = Sqr(Nx * Nx + Ny * Ny + Nz * Nz)
  368.     Nx = Nx / length
  369.     Ny = Ny / length
  370.     Nz = Nz / length
  371. End Sub
  372.  
  373. ' Return the proper shade for this face
  374. ' due to the indicated light source.
  375. Private Function SurfaceColor(ByVal light_sources As Collection, ByVal ambient_light As Integer) As Long
  376. Dim light As LightSource
  377. Dim Lx As Single
  378. Dim Ly As Single
  379. Dim Lz As Single
  380. Dim L_len As Single
  381. Dim Nx As Single
  382. Dim Ny As Single
  383. Dim Nz As Single
  384. Dim NdotL As Single
  385. Dim R As Integer
  386. Dim G As Integer
  387. Dim B As Integer
  388. Dim distance_factor As Single
  389.  
  390.     ' Find the unit surface normal. It is the
  391.     ' same for all the light sources.
  392.     UnitNormalVector Nx, Ny, Nz
  393.  
  394.     For Each light In light_sources
  395.         ' Find the unit vector pointing towards the light.
  396.         Lx = light.X - Points(1).coord(1)
  397.         Ly = light.Y - Points(1).coord(2)
  398.         Lz = light.Z - Points(1).coord(3)
  399.         L_len = Sqr(Lx * Lx + Ly * Ly + Lz * Lz)
  400.         Lx = Lx / L_len
  401.         Ly = Ly / L_len
  402.         Lz = Lz / L_len
  403.  
  404.         ' See how intense to make the color.
  405.         NdotL = Nx * Lx + Ny * Ly + Nz * Lz
  406.  
  407.         ' The light does not hit the top of the
  408.         ' surface if NdotL <= 0.
  409.         If NdotL > 0 Then
  410.             distance_factor = (light.Rmin + light.Kdist) / (L_len + light.Kdist)
  411.             R = R + light.Ir * DiffuseKr * NdotL * distance_factor
  412.             G = G + light.Ig * DiffuseKg * NdotL * distance_factor
  413.             B = B + light.Ib * DiffuseKb * NdotL * distance_factor
  414.         End If
  415.     Next light
  416.  
  417.     ' Add the ambient term.
  418.     R = R + ambient_light * AmbientKr
  419.     G = G + ambient_light * AmbientKg
  420.     B = B + ambient_light * AmbientKb
  421.  
  422.     ' Keep the color components <= 255.
  423.     If R > 255 Then R = 255
  424.     If G > 255 Then G = 255
  425.     If B > 255 Then B = 255
  426.  
  427.     ' Return the color.
  428.     SurfaceColor = RGB(R, G, B)
  429. End Function
  430. ' Compute a transformed normal vector for this polygon.
  431. Public Sub TransformedNormalVector(ByRef Nx As Single, ByRef Ny As Single, ByRef Nz As Single)
  432. Dim Ax As Single
  433. Dim Ay As Single
  434. Dim Az As Single
  435. Dim Bx As Single
  436. Dim By As Single
  437. Dim Bz As Single
  438.  
  439.     Ax = Points(2).trans(1) - Points(1).trans(1)
  440.     Ay = Points(2).trans(2) - Points(1).trans(2)
  441.     Az = Points(2).trans(3) - Points(1).trans(3)
  442.     Bx = Points(3).trans(1) - Points(2).trans(1)
  443.     By = Points(3).trans(2) - Points(2).trans(2)
  444.     Bz = Points(3).trans(3) - Points(2).trans(3)
  445.     m3Cross Nx, Ny, Nz, Ax, Ay, Az, Bx, By, Bz
  446. End Sub
  447.  
  448.  
  449. ' Add one or more points to the polygon.
  450. Public Sub AddPoints(ParamArray coord() As Variant)
  451. Dim num_pts As Integer
  452. Dim i As Integer
  453. Dim pt As Integer
  454.  
  455.     num_pts = (UBound(coord) + 1) \ 3
  456.     ReDim Preserve Points(1 To NumPts + num_pts)
  457.  
  458.     pt = 0
  459.     For i = 1 To num_pts
  460.         Points(NumPts + i).coord(1) = coord(pt)
  461.         Points(NumPts + i).coord(2) = coord(pt + 1)
  462.         Points(NumPts + i).coord(3) = coord(pt + 2)
  463.         Points(NumPts + i).coord(4) = 1#
  464.         pt = pt + 3
  465.     Next i
  466.  
  467.     NumPts = NumPts + num_pts
  468. End Sub
  469. ' Apply a transformation matrix which may not
  470. ' contain 0, 0, 0, 1 in the last column to the
  471. ' object.
  472. Public Sub ApplyFull(M() As Single)
  473. Dim i As Integer
  474.  
  475.     ' Do nothing if we are culled.
  476.     If IsCulled Then Exit Sub
  477.  
  478.     For i = 1 To NumPts
  479.         m3ApplyFull Points(i).coord, M, Points(i).trans
  480.     Next i
  481. End Sub
  482.  
  483. ' Apply a transformation matrix to the object.
  484. Public Sub Apply(M() As Single)
  485. Dim i As Integer
  486.  
  487.     ' Do nothing if we are culled.
  488.     If IsCulled Then Exit Sub
  489.  
  490.     For i = 1 To NumPts
  491.         m3Apply Points(i).coord, M, Points(i).trans
  492.     Next i
  493. End Sub
  494.  
  495. ' Draw the transformed points on a Form, Printer,
  496. ' or PictureBox.
  497. Public Sub Draw(ByVal pic As PictureBox, ByVal light_sources As Collection, ByVal ambient_light As Integer)
  498. Dim pts() As POINTAPI
  499. Dim i As Integer
  500.  
  501.     ' Do nothing if we are culled.
  502.     If IsCulled Then Exit Sub
  503.     If NumPts < 3 Then Exit Sub
  504.  
  505.     ReDim pts(1 To NumPts)
  506.     For i = 1 To NumPts
  507.         pts(i).X = Points(i).trans(1)
  508.         pts(i).Y = Points(i).trans(2)
  509.     Next i
  510.  
  511.     ' Find the right color for this object.
  512.     pic.FillColor = SurfaceColor(light_sources, ambient_light)
  513.  
  514.     ' Draw the polygon.
  515.     Polygon pic.hdc, pts(1), NumPts
  516. End Sub
  517. ' Return the minimum and maximum distances
  518. ' from the light source to points on this polygon.
  519. '
  520. ' This may not be the minimum distance to the
  521. ' plane, but it is close.
  522. Public Sub GetRminRmax(ByRef Rmin As Single, ByRef Rmax As Single, ByVal light_x As Single, ByVal light_y As Single, ByVal light_z As Single)
  523. Dim i As Integer
  524. Dim dx As Single
  525. Dim dy As Single
  526. Dim dz As Single
  527. Dim dist2 As Single
  528.  
  529.     Rmin = 1E+30
  530.     Rmax = -1E+30
  531.  
  532.     ' Do nothing if we are culled.
  533.     If IsCulled Then Exit Sub
  534.     If NumPts < 3 Then Exit Sub
  535.  
  536.     ' Get the min and max distance squared.
  537.     For i = 1 To NumPts
  538.         dx = light_x - Points(i).coord(1)
  539.         dy = light_y - Points(i).coord(2)
  540.         dz = light_z - Points(i).coord(3)
  541.         dist2 = dx * dx + dy * dy + dz * dz
  542.  
  543.         If Rmin > dist2 Then Rmin = dist2
  544.         If Rmax < dist2 Then Rmax = dist2
  545.     Next i
  546.  
  547.     ' Take square roots.
  548.     Rmin = Sqr(Rmin)
  549.     Rmax = Sqr(Rmax)
  550. End Sub
  551. ' Cull if any points are behind the center of
  552. ' projection.
  553. Public Sub ClipEye(ByVal R As Single)
  554. Dim pt As Integer
  555.  
  556.     ' Do nothing if we are already culled.
  557.     If IsCulled Then Exit Sub
  558.  
  559.     For pt = 1 To NumPts
  560.         If Points(pt).trans(3) >= R Then Exit For
  561.     Next pt
  562.  
  563.     If pt <= NumPts Then IsCulled = True
  564. End Sub
  565. ' Perform backface removal for the center
  566. ' of projection (X, Y, Z).
  567. Public Sub Cull(ByVal X As Single, ByVal Y As Single, ByVal Z As Single)
  568. Dim Ax As Single
  569. Dim Ay As Single
  570. Dim Az As Single
  571. Dim Nx As Single
  572. Dim Ny As Single
  573. Dim Nz As Single
  574.  
  575.     ' Compute a normal to the face.
  576.     NormalVector Nx, Ny, Nz
  577.  
  578.     ' Compute a vector from the center of
  579.     ' projection to the face.
  580.     Ax = Points(1).coord(1) - X
  581.     Ay = Points(1).coord(2) - Y
  582.     Az = Points(1).coord(3) - Z
  583.  
  584.     ' See if the vectors meet at an angle < 90.
  585.     IsCulled = (Ax * Nx + Ay * Ny + Az * Nz > -0.0001)
  586. End Sub
  587. ' Return the largest transformed Z value for this face.
  588. Public Function zmax() As Single
  589. Dim i As Integer
  590. Dim z_max As Single
  591.  
  592.     z_max = -1E+30
  593.     If IsCulled Then Exit Function
  594.  
  595.     For i = 1 To NumPts
  596.         If z_max < Points(i).trans(3) _
  597.             Then z_max = Points(i).trans(3)
  598.     Next i
  599.  
  600.     zmax = z_max
  601. End Function
  602.  
  603.  
  604.